package com.example.dzdph.service.ui

import android.content.pm.PackageManager
import android.os.Bundle
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
import com.example.dzdph.app.GlobalAppContext
import com.example.dzdph.node.AccessibilityNodeInfoAllocator
import com.example.dzdph.node.AccessibilityNodeInfoHelper

@Suppress("unused", "DEPRECATION", "MemberVisibilityCanBePrivate")
open class UiObject(
    info:Any?,
    private val allocator:AccessibilityNodeInfoAllocator?,
    private val depth : Int ,
    private val indexInParent: Int,
) :AccessibilityNodeInfoCompat(info) , UiObjectActions {

    constructor(
        info: Any?,
        allocator: AccessibilityNodeInfoAllocator,
        indexInParent: Int,
    ) : this(info, allocator, 0, indexInParent)

    @JvmOverloads
    constructor(
        info: Any?,
        depth: Int = 0,
        indexInParent: Int = -1,
    ) : this(info, null, depth, indexInParent)

    private fun getActionSymbolicName(action: Int) = when (action) {
        ACTION_FOCUS -> "ACTION_FOCUS"
        ACTION_CLEAR_FOCUS -> "ACTION_CLEAR_FOCUS"
        ACTION_SELECT -> "ACTION_SELECT"
        ACTION_CLEAR_SELECTION -> "ACTION_CLEAR_SELECTION"
        ACTION_CLICK -> "ACTION_CLICK"
        ACTION_LONG_CLICK -> "ACTION_LONG_CLICK"
        ACTION_ACCESSIBILITY_FOCUS -> "ACTION_ACCESSIBILITY_FOCUS"
        ACTION_CLEAR_ACCESSIBILITY_FOCUS -> "ACTION_CLEAR_ACCESSIBILITY_FOCUS"
        ACTION_NEXT_AT_MOVEMENT_GRANULARITY -> "ACTION_NEXT_AT_MOVEMENT_GRANULARITY"
        ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY -> "ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY"
        ACTION_NEXT_HTML_ELEMENT -> "ACTION_NEXT_HTML_ELEMENT"
        ACTION_PREVIOUS_HTML_ELEMENT -> "ACTION_PREVIOUS_HTML_ELEMENT"
        ACTION_SCROLL_FORWARD -> "ACTION_SCROLL_FORWARD"
        ACTION_SCROLL_BACKWARD -> "ACTION_SCROLL_BACKWARD"
        ACTION_CUT -> "ACTION_CUT"
        ACTION_COPY -> "ACTION_COPY"
        ACTION_PASTE -> "ACTION_PASTE"
        ACTION_SET_SELECTION -> "ACTION_SET_SELECTION"
        ACTION_EXPAND -> "ACTION_EXPAND"
        ACTION_COLLAPSE -> "ACTION_COLLAPSE"
        ACTION_SET_TEXT -> "ACTION_SET_TEXT"
        ACTION_DISMISS -> "ACTION_DISMISS"
        android.R.id.accessibilityActionDragStart -> "ACTION_DRAG_START"
        android.R.id.accessibilityActionDragDrop -> "ACTION_DRAG_DROP"
        android.R.id.accessibilityActionDragCancel -> "ACTION_DRAG_CANCEL"
        android.R.id.accessibilityActionScrollUp -> "ACTION_SCROLL_UP"
        android.R.id.accessibilityActionScrollLeft -> "ACTION_SCROLL_LEFT"
        android.R.id.accessibilityActionScrollDown -> "ACTION_SCROLL_DOWN"
        android.R.id.accessibilityActionScrollRight -> "ACTION_SCROLL_RIGHT"
        android.R.id.accessibilityActionPageDown -> "ACTION_PAGE_DOWN"
        android.R.id.accessibilityActionPageUp -> "ACTION_PAGE_UP"
        android.R.id.accessibilityActionPageLeft -> "ACTION_PAGE_LEFT"
        android.R.id.accessibilityActionPageRight -> "ACTION_PAGE_RIGHT"
        android.R.id.accessibilityActionShowOnScreen -> "ACTION_SHOW_ON_SCREEN"
        android.R.id.accessibilityActionScrollToPosition -> "ACTION_SCROLL_TO_POSITION"
        android.R.id.accessibilityActionContextClick -> "ACTION_CONTEXT_CLICK"
        android.R.id.accessibilityActionSetProgress -> "ACTION_SET_PROGRESS"
        android.R.id.accessibilityActionMoveWindow -> "ACTION_MOVE_WINDOW"
        android.R.id.accessibilityActionShowTextSuggestions -> "ACTION_SHOW_TEXT_SUGGESTIONS"
        android.R.id.accessibilityActionShowTooltip -> "ACTION_SHOW_TOOLTIP"
        android.R.id.accessibilityActionHideTooltip -> "ACTION_HIDE_TOOLTIP"
        android.R.id.accessibilityActionPressAndHold -> "ACTION_PRESS_AND_HOLD"
        android.R.id.accessibilityActionImeEnter -> "ACTION_IME_ENTER"
        else -> "ACTION_UNKNOWN"
    }

    fun actionNames() = actionList.map { action ->
        getActionSymbolicName(action.id).let { actionName ->
            if (actionName == "ACTION_UNKNOWN" && action.label != null) {
                action.label.toString()
            } else {
                actionName
            }
        }
    }

    fun hasAction(vararg actions: Any): Boolean {
        return actionNames().let { actionNames ->
            actions.all {
                when (it) {
                    is Int -> hasAction(getActionSymbolicName(it))
                    is Double -> hasAction(getActionSymbolicName(it.toInt()))
                    is String -> actionNames.contains(if (it.startsWith("ACTION_")) it else "ACTION_$it")
                    else -> false
                }
            }
        }
    }

    override fun performAction(action: Int, vararg arguments: ActionArgument): Boolean {
        return performAction(action, Bundle().apply { arguments.forEach { it.putIn(this) } })
    }

    override fun performAction(action: Int, bundle: Bundle): Boolean = try {
        when (bundle.isEmpty) {
            true -> super.performAction(action)
            else -> super.performAction(action, bundle)
        }
    } catch (e: IllegalStateException) {
        // FIXME by Stardust on May 5, 2017.
        false.also { e.printStackTrace() }
    }

    override fun performAction(action: Int): Boolean = try {
        super.performAction(action)
    } catch (e: IllegalStateException) {
        // FIXME by Stardust on May 5, 2017.
        false.also { e.printStackTrace() }
    }

    open fun id(): String? = fullId()

    fun fullId(): String? = viewIdResourceName

    open fun packageName(): String? = packageName?.toString()

    fun idHex(): String? {
        val pkg = packageName() ?: return null
        val fullId = viewIdResourceName ?: return null
        val resources = try {
            GlobalAppContext.get().packageManager.getResourcesForApplication(pkg)
        } catch (e: PackageManager.NameNotFoundException) {
            e.printStackTrace()
            return null
        }
        return "0x${Integer.toHexString(resources.getIdentifier(fullId, null, null))}"
    }

    open fun desc(): String? = contentDescription?.toString()

    open fun bounds() = AccessibilityNodeInfoHelper.getBoundsInScreen(this)

    open fun text() = text?.toString() ?: ""

    open fun className(): String? = className?.toString()

    open fun clickable() = isClickable

    open fun longClickable() = isLongClickable

    open fun scrollable() = isScrollable

    open fun checked() = isChecked

    fun indexInParent() = indexInParent

    open fun childCount() = childCount

    open fun depth() = depth

    open fun enabled() = isEnabled

    fun editable() = isEditable

    open fun focusable() = isFocusable

    open fun checkable() = isCheckable

    open fun selected() = isSelected

    open fun visibleToUser() = isVisibleToUser

    open fun focused() = isFocused

    open fun rowCount() = collectionInfo?.rowCount ?: 0

    open fun columnCount() = collectionInfo?.columnCount ?: 0

    open fun row() = collectionItemInfo?.rowIndex ?: -1

    open fun column() = collectionItemInfo?.columnIndex ?: -1

    open fun rowSpan() = collectionItemInfo?.rowSpan ?: -1

    open fun columnSpan() = collectionItemInfo?.columnSpan ?: -1

    fun isSingleton() = siblingCount() == 1

    fun siblingCount() = parent()?.childCount ?: 1

    open fun parent(): UiObject? = try {
        super.getParent()?.let { node ->
            UiObject(node.unwrap(), depth - 1, node.parent?.run {
                // @Dubious by SuperMonster003 on May 26, 2022.
                //  ! May lead to poor performance?
                //  ! zh-CN: 可能导致性能不佳?
                (0 until childCount).firstOrNull { getChild(it) == node } ?: -1
            } ?: 0)
        }
    } catch (e: IllegalStateException) {
        // FIXME by Stardust on May 5, 2017.
        null.also { e.printStackTrace() }
    }

    open fun child(i: Int): UiObject? {
        if (i < 0) {
            return (i + childCount).takeIf { it >= 0 }?.let { child(it) }
        }
        return try {
            super.getChild(i)?.run { UiObject(unwrap(), depth + 1, i) }
        } catch (e: IllegalStateException) {
            // FIXME by Stardust on May 5, 2017.
            null.also { e.printStackTrace() }
        }
    }

    fun children() = List(childCount) { child(it) }.let { UiObjectCollection.of(it) }

    open fun sibling(i: Int): UiObject? = try {
        if (i == indexInParent) this
        else parent()?.child(i)
    } catch (e: ArrayIndexOutOfBoundsException) {
        null.also { e.printStackTrace() }
    }

    fun firstSibling() = sibling(0)

    fun lastSibling() = sibling(-1)

    fun firstChild() = child(0)

    fun lastChild() = child(childCount - 1)

    override fun hashCode() = listOf(
        "${packageName()}",
        "$parent",
        "${id()}",
        "${fullId()}",
        "${idHex()}",
        "${desc()}",
        text(),
        "${bounds()}",
        "${className()}",
        "${clickable()}",
        "${longClickable()}",
        "${scrollable()}",
        "${indexInParent()}",
        "${childCount()}",
        "${depth()}",
        "${checked()}",
        "${enabled()}",
        "${editable()}",
        "${focusable()}",
        "${checkable()}",
        "${selected()}",
        "$isDismissable",
        "${visibleToUser()}",
        "$isContextClickable",
        "${focused()}",
        "$isAccessibilityFocused",
        "${rowCount()}",
        "${columnCount()}",
        "${row()}",
        "${column()}",
        "${rowSpan()}",
        "${columnSpan()}",
        "$drawingOrder",
        "${actionNames()}",
        "${isSingleton()}",
        "${firstSibling()}",
        "${lastSibling()}",
        "${firstChild()}",
        "${lastChild()}",
    ).toHashCode()

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other !is UiObject) return false

        return packageName() == other.packageName() &&
                parent == other.parent &&
                id() == other.id() &&
                fullId() == other.fullId() &&
                idHex() == other.idHex() &&
                desc() == other.desc() &&
                text() == other.text() &&
                bounds() == other.bounds() &&
                className() == other.className() &&
                clickable() == other.clickable() &&
                longClickable() == other.longClickable() &&
                scrollable() == other.scrollable() &&
                indexInParent() == other.indexInParent() &&
                childCount() == other.childCount() &&
                depth() == other.depth() &&
                checked() == other.checked() &&
                enabled() == other.enabled() &&
                editable() == other.editable() &&
                focusable() == other.focusable() &&
                checkable() == other.checkable() &&
                selected() == other.selected() &&
                isDismissable == other.isDismissable &&
                visibleToUser() == other.visibleToUser() &&
                isContextClickable == other.isContextClickable &&
                focused() == other.focused() &&
                isAccessibilityFocused == other.isAccessibilityFocused &&
                rowCount() == other.rowCount() &&
                columnCount() == other.columnCount() &&
                row() == other.row() &&
                column() == other.column() &&
                rowSpan() == other.rowSpan() &&
                columnSpan() == other.columnSpan() &&
                drawingOrder == other.drawingOrder &&
                actionNames() == other.actionNames() &&
                isSingleton() == other.isSingleton() &&
                "${firstSibling()}" == "${other.firstSibling()}" &&
                "${lastSibling()}" == "${other.lastSibling()}" &&
                "${firstChild()}" == "${other.firstChild()}" &&
                "${lastChild()}" == "${other.lastChild()}"
    }
}

